package cn.tannn.surenessdemo.user.service.impl;

import cn.jdevelops.entity.basics.util.UUIDUtils;
import cn.jdevelops.exception.exception.BusinessException;
import cn.jdevelops.http.core.IpUtil;
import cn.tannn.surenessdemo.constant.HeaderConstant;
import cn.tannn.surenessdemo.constant.RoleConstant;
import cn.tannn.surenessdemo.constant.UserConstant;
import cn.tannn.surenessdemo.controller.dto.LoginDTO;
import cn.tannn.surenessdemo.controller.dto.RegisterDTO;
import cn.tannn.surenessdemo.controller.vo.AuthUserVO;
import cn.tannn.surenessdemo.exception.UserException;
import cn.tannn.surenessdemo.redis.service.RedisService;
import cn.tannn.surenessdemo.user.dao.AuthUserDao;
import cn.tannn.surenessdemo.user.dao.AuthUserRoleBindDao;
import cn.tannn.surenessdemo.user.entity.AuthUser;
import cn.tannn.surenessdemo.user.entity.AuthUserRoleBind;
import cn.tannn.surenessdemo.enums.ExceptionEnum;
import cn.tannn.surenessdemo.sureness.processor.RedisAccount;
import cn.tannn.surenessdemo.user.service.AccountService;
import cn.tannn.surenessdemo.util.JwtTokenUtil;
import com.usthe.sureness.util.SurenessCommonUtil;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * @author tomsun28
 * @date 10:58 2019-08-04
 */
@Service
@RequiredArgsConstructor
public class AccountServiceImpl implements AccountService {

    private final AuthUserDao authUserDao;

    private final AuthUserRoleBindDao userRoleBindDao;

    private final RedisService redisService;



    @Override
    public String authenticateAccount(LoginDTO login) {
        Optional<AuthUser> authUserOptional = authUserDao.findAuthUserByUsername(login.getUsername());
        if (!authUserOptional.isPresent()) {
            throw new UserException(ExceptionEnum.USER_EXIST_ERROR);
        }
        AuthUser authUser = authUserOptional.get();

        if (login.getPassword() == null) {
            throw new UserException(ExceptionEnum.USER_PASSWORD_ERROR);
        }
        if (authUser.disabledAccount()) {
            throw new BusinessException(ExceptionEnum.DISABLED_ACCOUNT.getMessage());
        }
        if (authUser.disabledAccount()) {
            throw new BusinessException(ExceptionEnum.EXCESSIVE_ATTEMPTS_ACCOUNT.getMessage());
        }
        // md5 with salt
        String password = authUser.getMd5Password(login.getPassword());
        if(!password.equals(authUser.getPassword())){
            throw new UserException(ExceptionEnum.USER_PASSWORD_ERROR);
        }else {
            //  加载加载用户权限 jwt中放权限的话,修改用户角色后就必须让用户强制下线
            List<String> ownRole = loadAccountRoles(login.getUsername());
            String jwt = JwtTokenUtil.issueJwt(login.getUsername(), ownRole);
            // 用户token存在如redis
            redisService.storageUserToken(login.getUsername(), jwt);
            // 存储到redis中，验证要用
            getRedisAccount(login.getUsername(), authUserOptional);
            return jwt;
        }

    }

    @Override
    public List<String> loadAccountRoles(String username) {
        List<String> accountOwnRoles = authUserDao.findAccountOwnRoles(username);
        // todo redis-memory 加载用户权限到redis中，鉴权时从redis中取，而不是从token中取（这样不安全）
        redisService.storageUserRole(username, accountOwnRoles);
        return accountOwnRoles;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean registerAccount(RegisterDTO register, HttpServletRequest request) {
        if (isAccountExist(register.getUsername())) {
            return false;
        }
        String salt = SurenessCommonUtil.getRandomString(6);
        String userCode = UUIDUtils.getInstance().generateShortUuid();
        AuthUser authUser = register.to(AuthUser.class);
        authUser.setCreateIp(IpUtil.getPoxyIp(request));
        authUser.setSalt(salt);
        authUser.setPassword(authUser.getMd5Password());
        authUser.setStatus(1);
        authUser.setCode(userCode);
        authUserDao.save(authUser);
        // 注册就给常规角色
        AuthUserRoleBind authUserRoleBind = new AuthUserRoleBind();
        authUserRoleBind.setRoleCode(RoleConstant.COMMON_USER);
        authUserRoleBind.setUserCode(userCode);
        userRoleBindDao.save(authUserRoleBind);
        return true;
    }

    @Override
    public boolean isAccountExist(String userName) {
        Optional<AuthUser> authUserOptional = authUserDao.findAuthUserByUsername(userName);
        return authUserOptional.isPresent();
    }

    @Override
    public RedisAccount loadAccountStatus(String username) {
        RedisAccount surenessAccount = redisService.loadUserStatus(username);
        if(Objects.isNull(surenessAccount)){
            Optional<AuthUser> authUserOptional = authUserDao.findAuthUserByUsername(username);
            // 密码的验证在 authenticated Subject.getCredential()
            // 由于这里除了token的验证之外还以 username/password的验证，所以可能需要设置ownRoles，我这里没有设置
            return getRedisAccount(username, authUserOptional);
        }else {
            return surenessAccount;
        }
    }



    @Override
    public RedisAccount loadAccountNoRedis(String username) {
        Optional<AuthUser> authUserOptional = authUserDao.findAuthUserByUsername(username);
        return getRedisAccount(username, authUserOptional);
    }




    @Override
    public boolean authorityUserRole(String username, String roleCode) {
        Optional<AuthUser> optional = authUserDao.findAuthUserByUsername(username);
        if (!optional.isPresent()) {
            return false;
        }
        String userCode = optional.get().getCode();
        AuthUserRoleBind userRoleBindDO = AuthUserRoleBind.builder().userCode(userCode).roleCode(roleCode).build();
        userRoleBindDao.save(userRoleBindDO);
        // todo redis 存储用户角色 - 新增用户-角色
        loadAccountRoles(username);
        return true;
    }

    @Override
    public boolean deleteAuthorityUserRole(String username, String roleCode) {
        Optional<AuthUser> optional = authUserDao.findAuthUserByUsername(username);
        if (!optional.isPresent()) {
            return false;
        }
        String userCode = optional.get().getCode();
        userRoleBindDao.deleteRoleResourceBind(roleCode, userCode);
        // todo redis 存储用户角色 - 删除用户-角色
        loadAccountRoles(username);
        return true;
    }

    @Override
    public List<AuthUser> findAll() {
        return authUserDao.findAll();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void settingStatus(String username, Integer status) {
        authUserDao.settingStatus(username, status);
        Optional<AuthUser> authUserByUsername = authUserDao.findAuthUserByUsername(username);
        // todo redis存储用户状态 和 存储用户角色 - 设置用户状态
        getRedisAccount(username, authUserByUsername);
    }

    @Override
    public AuthUserVO getUser(String username) {
        return authUserDao.findByUsername(username).to(AuthUserVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initUserAdmin(String password) {
        AuthUser authUser = new AuthUser();
        authUser.setSalt("admin");
        authUser.setCode(UserConstant.ADMINISTRATORS);
        authUser.setCreateIp("localhost");
        authUser.setNickName("超级管理员");
        authUser.setStatus(1);
        authUser.setUsername(UserConstant.ADMINISTRATORS);
        authUser.setPassword(authUser.getMd5Password(password));
        authUserDao.save(authUser);

        // 设置权限 不管角色有没有都给他先设置上
        AuthUserRoleBind authUserRoleBind = new AuthUserRoleBind();
        authUserRoleBind.setUserCode(UserConstant.ADMINISTRATORS);
        authUserRoleBind.setRoleCode(RoleConstant.ADMINISTRATORS);
        List<String> roles = redisService.loadUserRole(UserConstant.ADMINISTRATORS);
        if(Objects.isNull(roles)){
            roles = new ArrayList<>();
        }
        roles.add(RoleConstant.ADMINISTRATORS);
        userRoleBindDao.save(authUserRoleBind);
    }

    /**
     * 退出登录
     * @param request request
     */
    @Override
    public void logout(HttpServletRequest request){
        try {
            String token = request.getHeader(HeaderConstant.HEADER_TOKEN);
            Claims claims = JwtTokenUtil.parseJwtNoExpiredJwtException(token);
            // todo 删除token
            redisService.removeUserToken(claims.getSubject());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Nullable
    private RedisAccount getRedisAccount(String username, Optional<AuthUser> authUserOptional) {
       try {
           if (authUserOptional.isPresent()) {
               AuthUser authUser = authUserOptional.get();
               RedisAccount.RedisAccountBuilder accountBuilder = RedisAccount.builder().appId(username)
                       .password(authUser.getPassword())
                       .salt(authUser.getSalt())
                       //  是否禁用
                       .disabledAccount(authUser.disabledAccount())
                       // 是否锁定账户
                       .excessiveAttempts(authUser.excessiveAttempts());
               RedisAccount build = accountBuilder.build();
               // todo 加载用户状态到redis
               redisService.storageUserStatus(build);
               return build;
           }
       }catch (Exception e){
           e.printStackTrace();
       }
        return null;
    }
}
